package p25_控制器方法执行流程;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;

import java.lang.reflect.Method;

/**
 * 控制器方法是如何调用到控制器的目标方法的?
 * 首先看看控制器方法(HandlerMethod)的结构
 *    1.组合了bean对象(目标控制器类bean)
 *    2.组合了method对象(目标控制器的方法对象)
 *  ServletInvocableHandlerMethod是HandlerMethod的子类，它又额外组合了下面四个组件
 *      1.WebDataBinderFactory：从该工厂获取到WebDataBinder绑定器
 *      2.ParameterNameDiscover：获取参数名的组件
 *      3.HandlerMethodArgumentResolverComposite：参数解析器组合器
 *      4.HandlerMethodReturnValueResolverComposite：返回值解析器组合器
 *      这四个组件都是前面讲解过的，ServletInvocableHandlerMethod使用这四个组件外加父类的两个组件，都能成功调用控制器方法
 *
 * <img src="img/控制器方法对象结构.png" />
 * <img src="img/控制器方法执行流程1.png" />
 * <img src="img/控制器方法执行流程2.png" />
 * 本节知识点：
 *   被ServletModelAttributeMethodProcessor解析的控制器参数，会被作为模型数据放入ModelAndViewContainer
 *   模型数据是什么？
 *      前后端不分离的时候，springmvc最后一步是视图渲染，会将模型数据填充到html模板中，然后将html响应给前端进行渲染
 */
public class _HandlerMethod {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        //模拟请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name", "yt");
        request.setParameter("age", "233");

        /**
         * 调用控制器方法使用的HandlerMethod的子类ServletInvocableHandlerMethod
         * 控制器方法对象对应的控制器的一个方法，将来控制器方法对象使用反射调用目标对象，所有创建控制器方法对象需要控制器对象，和方法对象
         * ServletInvocableHandlerMethod需要以下几个组件
         *  1.WebDataBinderFactory 负责对象绑定、类型转换
         *  2.ParameterNameDiscoverer 负责参数名解析
         *  3.HandlerMethodArgumentResolverComposite 负责解析参数
         *  4.HandlerMethodReturnValueHandlerComposite 负责处理返回值(这里暂时不演示)
         * 下面创建了一个ServletInvocableHandlerMethod并赋予其五个组件(没有返回值处理器)
         */
        Method method = new WebConfig.MyController().getClass().getMethod("foo", WebConfig.Person.class, WebConfig.Person.class, String.class);
        ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new WebConfig.MyController(), method);
        handlerMethod.setDataBinderFactory(new ServletRequestDataBinderFactory(null, null));
        handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
        handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolverComposite(context));

        /**
         * 创建好了ServletInvocableHandlerMethod，通过下面方法，来调用控制器的目标方法，并且会根据自动解析好方法参数
         * 需要的参数: 1.请求对象数据、2.ModelAndViewContainer(容器，用来保存模型数据，参数解析或者返回处理，都会产生model数据)、3.额外参数
         * 下面方法会间接调用到WebConfig.MyController类的foo方法，并通过参数解析器给foo方法的参数赋值
         */
        ModelAndViewContainer container = new ModelAndViewContainer();
        handlerMethod.invokeAndHandle(new ServletWebRequest(request), container, null);
        context.close();

        /**
         * foo方法的参数由ServletModelAttributeMethodProcessor(不带或省略了@ModelAttribute的参数)负责解析(底层是通过WebDataBinderFactory负责值的绑定)
         * ServletModelAttributeMethodProcessor似乎只能解析带有属性的javaBean对象，基本数据类型或其包装类(如 String 或 Integer)就算加了@ModelAttribute也不会被正确解析
         * 而ServletModelAttributeMethodProcessor会将解析出的参数放入ModelAndViewContainer
         * 打印ModelAndViewContainer内部的数据，发现有个person=WebConfig.Person(name=yt, age=233)，还有个数据是参数校验的数据
         */
        System.out.println(container.getModel());
    }

    public static HandlerMethodArgumentResolverComposite getArgumentResolverComposite(AnnotationConfigApplicationContext context) {
        HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
        composite.addResolvers(

                /**
                 * 解析@ModelAttribute模型数据，并将解析出的模型对象存入ModelAndViewContainer.参数false:必须有@ModelAttribute;true:可以省略@ModelAttribute
                 * 该参数解析器解析的对象，才能作为模型数据
                 */
                new ServletModelAttributeMethodProcessor(false),
                /**
                 * 处理省略了@ModelAttribute的模型参数
                 * 该参数解析器解析的对象，才能作为模型数据
                 */
                new ServletModelAttributeMethodProcessor(true)
        );
        return composite;
    }
}
